查看原文
其他

编写优质嵌入式C程序(二)

zhzht19861011 嵌入式大杂烩 2021-01-31

点击上方「嵌入式大杂烩」,选择「置顶公众号」第一时间获取免费资料!   


前言


今天,本公众号改新名字啦,由”正念君编程学习笔记“更名为”嵌入式大杂烩“。



请记住我的名字:嵌入式打杂的,哈哈。(嵌入式大杂烩)。与此同时,与本公众号相应的个人博客网站正在搭建中,之后公众号推文也会同步更新到博客中,敬请期待,欢迎关注!为什么不用CSDN或者博客园?其实在这两个上面也有账号也有发文章。但是,还是想自己尝试着做一个。个人博客首页如下:



博客的名字就先保留着原来的名字吧 这个个人博客网站是借助于github上一位大神的开源项目,拿过来修改的。建议你也做一个,

教程为:

https://blog.csdn.net/Dogfights/article/details/80144332


最近,也在看一位资深嵌入式软件工程师的一些经验总结,觉得很不错,现整理如下,与大家一同学习。

原文:

http://blog.csdn.net/zhzht19861011/article/details/45508029


不可轻视的优先级


C语言有32个关键字,却有34个运算符。要记住所有运算符的优先级是困难的。稍不注意,你的代码逻辑和实际执行就会有很大出入。比如下面将BCD码转换为十六进制数的代码:

result=(uTimeValue>>4)*10+uTimeValue&0x0F;


这里uTimeValue存放的BCD码,想要转换成16进制数据,实际运行发现,如果uTimeValue的值为0x23,按照我设定的逻辑,result的值应该是0x17,但运算结果却是0x07。经过种种排查后,才发现’+’的优先级是大于’&’的,相当于(uTimeValue>>4)*10+uTimeValue与0x0F位与,结果自然与逻辑不符。符合逻辑的代码应该是:

result=(uTimeValue>>4)*10+(uTimeValue&0x0F);


不合理的#define会加重优先级问题,让问题变得更加隐蔽。

#define READSDA IO0PIN&(1<<11) //读IO口p0.11的端口状态
if(READSDA==(1<<11)) //判断端口p0.11是否为高电平
{
//其它代码
}


编译器在编译后将宏带入,原代码语句变为:  

if(IO0PIN&(1<<11) ==(1<<11))
{
//其它代码
}


运算符'=='的优先级是大于'&'的,代码IO0PIN&(1<<11) ==(1<<11))等效为IO0PIN&0x00000001:判断端口P0.0是否为高电平,这与原意相差甚远。因此,使用宏定义的时候,最好将被定义的内容用括号括起来。


按照常规方式使用时,可能引起误会的运算符还有很多,如下表所示:


C语言的运算符当然不会只止步于数目繁多!有一个简便方法可以避免优先级问题:不清楚的优先级就加上”()”,但这样至少有会带来两个问题:

(1)过多的括号影响代码的可读性,包括自己和以后的维护人员;

(2)别人的代码不一定用括号来解决优先级问题,但你总要读别人的代码 

无论如何,在嵌入式编程方面,该掌握的基础知识,偷巧不得。建议花一些时间,将优先级顺序以及容易出错的优先级运算符理清几遍。


隐式转换

C语言的设计理念一直被人吐槽,因为它认为C程序员完全清楚自己在做什么,其中一个证据就是隐式转换。C语言规定,不同类型的数据(比如char和int型数据)需要转换成同一类型后,才可进行计算。如果你混合使用类型,比如用char类型数据和int类型数据做减法,C使用一个规则集合来自动(隐式的)完成类型转换。这可能很方便,但也很危险。


这就要求我们理解这个转换规则并且能应用到程序中去!


(1)当出现在表达式里时,有符号和无符号的char和short类型都将自动被转换为int类型,在需要的情况下,将自动被转换为unsigned int(在short和int具有相同大小时)。这称为类型提升。


提升在算数运算中通常不会有什么大的坏处,但如果位运算符 ~ 和 << 应用在基本类型为unsigned char或unsigned short 的操作数,结果应该立即强制转换为unsigned char或者unsigned short类型(取决于操作时使用的类型)。

uint8_t port =0x5aU;
uint8_t result_8;
result_8= (~port) >> 4;


假如我们不了解表达式里的类型提升,认为在运算过程中变量port一直是unsigned char类型的。我们来看一下运算过程:~port结果为0xa5,0xa5>>4结果为0x0a,这是我们期望的值。但实际上,result_8的结果却是0xfa!


在ARM结构下,int类型为32位。变量port在运算前被提升为int类型:~port结果为0xffffffa5,0xa5>>4结果为0x0ffffffa,赋值给变量result_8,发生类型截断(这也是隐式的!),result_8=0xfa。经过这么诡异的隐式转换,结果跟我们期望的值,已经大相径庭!正确的表达式语句应该为:

result_8=(unsigned char) (~port) >> 4; /*强制转换*/


(2)在包含两种数据类型的任何运算里,两个值都会被转换成两种类型里较高的级别。类型级别从高到低的顺序是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。

(PS:关于隐式转换可查看我的往期笔记【重要】关于有符号数与无符号数的一些总结


这种类型提升通常都是件好事,但往往有很多程序员不能真正理解这句话,比如下面的例子(int类型表示16位)。

uint16_t u16a = 40000; /* 16位无符号变量*/
uint16_t u16b= 30000; /*16位无符号变量*/
uint32_t u32x; /*32位无符号变量 */
uint32_t u32y;
u32x = u16a +u16b; /* u32x = 70000还是4464 ? */
u32y =(uint32_t)(u16a + u16b); /* u32y = 70000 还是4464 ? */


u32x和u32y的结果都是4464(70000%65536)!不要认为表达式中有一个高类别uint32_t类型变量,编译器都会帮你把所有其他低类别都提升到uint32_t类型。正确的书写方式:

u32x = (uint32_t)u16a +(uint32_t)u16b;


或者:

u32x = (uint32_t)u16a + u16b;

后一种写法在本表达式中是正确的,但是在其它表达式中不一定正确,比如:

uint16_t u16a,u16b,u16c;
uint32_t u32x;
u32x= u16a + u16b + (uint32_t)u16c;/*错误写法,u16a+ u16b仍可能溢出*/


(3)在赋值语句里,计算的最后结果被转换成将要被赋予值的那个变量的类型。这一过程可能导致类型提升也可能导致类型降级。降级可能会导致问题。比如将运算结果为321的值赋值给8位char类型变量。程序必须对运算时的数据溢出做合理的处理。很多其他语言,像Pascal(C语言设计者之一曾撰文狠狠批评过Pascal语言),都不允许混合使用类型,但C语言不会限制你的自由,即便这经常引起Bug。


(4)当作为函数的参数被传递时,char和short会被转换为int,float会被转换为double。


当不得已混合使用类型时,一个比较好的习惯是使用类型强制转换。强制类型转换可以避免编译器隐式转换带来的错误,同时也向以后的维护人员传递一些有用信息。这有个前提:你要对强制类型转换有足够的了解!下面总结一些规则:


并非所有强制类型转换都是由风险的,把一个整数值转换为一种具有相同符号的更宽类型时,是绝对安全的。


精度高的类型强制转换为精度低的类型时,通过丢弃适当数量的最高有效位来获取结果,也就是说会发生数据截断,并且可能改变数据的符号位。


精度低的类型强制转换为精度高的类型时,如果两种类型具有相同的符号,那么没什么问题;需要注意的是负的有符号精度低类型强制转换为无符号精度高类型时,会不直观的执行符号扩展,例如:

unsigned int bob;
signed char fred = -1;
bob=(unsigned int )fred; /*发生符号扩展,此时bob为0xFFFFFFFF*/



Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存